/**
 *
 * The MIT License
 *
 * Copyright (c) 2008 Board of Trustees, Leland Stanford Jr. University. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.macdadi.ks.client.views;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.gwtext.client.core.EventObject;
import com.gwtext.client.core.RegionPosition;
import com.gwtext.client.util.Format;
import com.gwtext.client.widgets.Button;
import com.gwtext.client.widgets.MessageBox;
import com.gwtext.client.widgets.Panel;
import com.gwtext.client.widgets.TabPanel;
import com.gwtext.client.widgets.Window;
import com.gwtext.client.widgets.event.ButtonListenerAdapter;
import com.gwtext.client.widgets.layout.BorderLayout;
import com.gwtext.client.widgets.layout.BorderLayoutData;

import org.macdadi.core.client.auth.AuthService;
import org.macdadi.core.client.auth.AuthenticatedUser;
import org.macdadi.core.client.auth.AuthorizationException;
import org.macdadi.core.client.domain.DataService;
import org.macdadi.core.client.domain.DataWriteService;
import org.macdadi.core.client.domain.Goal;
import org.macdadi.core.client.domain.ProjectInfo;
import org.macdadi.core.client.ui.DefaultItemEditor;
import org.macdadi.core.client.ui.EditableTree;
import org.macdadi.ks.client.View;
import org.macdadi.ks.client.ViewContainer;
import org.macdadi.ks.client.components.ObjectDetails;

import java.util.ArrayList;

/**
 * @author Ben Suter <ben.suter@gmail.com>
 */
public class Goals extends View implements TreeListener {
	protected EditableTree tree;
	protected HorizontalPanel mainPanel;
	protected VerticalPanel treePanel;
	protected ObjectDetails goalsDetailPanel;
	protected ArrayList expandedItems = new ArrayList();
	protected ProjectInfo project;
	private static String instructionsString;
	private static String bugsString;
	Button instructionsButton;

	protected EditableTree.ItemEditor goalEdit, goalAdd, goalRemove;

	public static ViewInfo init() {
		String info = "<p>As a team, arrive at a common hierarchy of goals</p>";

		return new ViewInfo("Goals", info) {
			public View createInstance(ViewContainer container) {
				return new Goals(container);
			}

			public String getColor() {
				return "#fe9915";
			}
		};
	}

	public Goals(ViewContainer container) {
		this.container = container;
		project = container.getCurrentProject();

		mainPanel = new HorizontalPanel();
		mainPanel.setStyleName("macdadi-Goals");
		mainPanel.setWidth("98%");
		
		treePanel = new VerticalPanel();
		treePanel.setWidth("850");

		tree = new EditableTree();
		tree.addTreeListener(this);

		goalEdit = new GoalEdit(updateTree, project);
		goalAdd = new GoalAdd(expandSelectedThenUpdateTree, project);
		goalRemove = new GoalRemove(updateTree, project);

		Panel space = new Panel();
		space.setHeight(20);
		space.setBorder(false);

		instructionsString = "<p> On this page, the user is given an Editable Tree. For each node you can: </p>" +
		"<p> ~~~ </p> " +
		"<p><b>   1) Edit the name of the node </b></p>" +
		"<p><b>   2) Remove the node and its subnodes </b></p>" +
		"<p><b>   3) Add a subnode </b></p>" +
		"<p> ~~~ </p> " +
		"<p> Remarks - To perform these options, simply click on the desired node and three widgets will appear (a plus, minus, and a check). The plus widget adds a node; the minus widget removes the nodes; and the check widget renames the node. </p>" ;
		bugsString = "<p> None ATM </p>" +
		"<p> ~~~ </p>" +
		"<p> Discover any bugs? Please contact me at: <i>jwei512@stanford.edu</i> </p>";

		HTML instructionsHTML = new HTML(instructionsString);

		TabPanel tabPanel = new TabPanel();  
		tabPanel.setActiveTab(0);

		Panel tab1 = new Panel();  
		tab1.setTitle("Instructions");  
		tab1.add(instructionsHTML);
		tab1.setAutoScroll(true);  

		Panel tab2 = new Panel();  
		tab2.setTitle("Current Bugs/Fixes");  
		tab2.setHtml(bugsString);  
		tab2.setAutoScroll(true);

		tabPanel.add(tab1);
		tabPanel.add(tab2);

		BorderLayoutData centerData = new BorderLayoutData(RegionPosition.CENTER);  
		centerData.setMargins(3, 0, 3, 3);

		final Window window = new Window();
		window.setTitle("Instructions for Goals Tab");  
		window.setClosable(true);  
		window.setWidth(600);  
		window.setHeight(350);  
		window.setPlain(true); 
		window.setLayout(new BorderLayout());  
		window.add(tabPanel, centerData);
		window.setCloseAction(Window.HIDE);

		instructionsButton = new Button("Instructions");  
		instructionsButton.addListener(new ButtonListenerAdapter() {
			public void onClick(Button button, EventObject e) {
				window.show();
			}  
		});

		goalsDetailPanel = new ObjectDetails(1,"goal", container.getCurrentAuthToken());

		treePanel.add(instructionsButton);
		treePanel.add(space);
		treePanel.add(tree);
		mainPanel.add(treePanel);
		mainPanel.add(goalsDetailPanel);
		initWidget(mainPanel);
	}

	public void onShow() {
		populateTree(project);
		AuthenticatedUser user = container.getCurrentUser();

		if ( user.isAuthorizedOperation(project.getId(), AuthService.TARGET_GOALS, AuthService.OPERATION_CREATE))
			tree.addItemEditor(goalAdd);

		if ( user.isAuthorizedOperation(project.getId(), AuthService.TARGET_GOALS, AuthService.OPERATION_REMOVE))
			tree.addItemEditor(goalRemove);

		if ( user.isAuthorizedOperation(project.getId(), AuthService.TARGET_GOALS, AuthService.OPERATION_UPDATE))
			tree.addItemEditor(goalEdit);
	}

	public void onHide() {
		tree.stopEditing();
		tree.removeItemEditor(goalAdd);
		tree.removeItemEditor(goalRemove);
		tree.removeItemEditor(goalEdit);
		tree.clear();
	}

	/**
	 * Add a new root item to the tree, whose label initially displays a "loading ..." message.
	 * Then it retrieves all the root-level goals for this project and adds each one to the
	 * tree. As it does so, the children of these goals are added recursively. See addChild().
	 * <p/>
	 * Finally the rootItem is expanded, so that the first level of goals
	 * is always visible.
	 *
	 * @param project the project from which the tree domain should be pulled
	 */
	protected void populateTree(final ProjectInfo project) {
		final TreeItem rootItem = new TreeItem();
		rootItem.setText("Loading goals for " + project.getName());
		tree.addItem(rootItem);

		DataService.App.getInstance().getGoals(project.getId(), container.getCurrentAuthToken(), new AsyncCallback() {
			public void onFailure(Throwable caught) {
				GWT.log("Failed to populate the goals tree", caught);
				if (caught instanceof AuthorizationException) {
					rootItem.setText("Please login to view goals for " + project.getName());
				}
			}

			public void onSuccess(Object result) {
				//rootItem.setText("Goals for " + project.getName());
				rootItem.setHTML("<p><font size = 5><b> Project Goals </b></font></p>");

				Goal[] roots = (Goal[]) result;
				for (int i = 0; i < roots.length; i++) {
					addChild(rootItem, roots[i], true, true);
				}

				/* Expand the first and second levels */
				rootItem.setState(true, true);
				for (int i = 0; i < rootItem.getChildCount(); i++ ) {
					rootItem.getChild(i).setState(true, true);
				}
			}
		});
	}

	/**
	 * Adds the given Goal as a child TreeItem to the parent TreeItem.
	 * Optionally steps into the given Goal's children and does the same there,
	 * etc. recursively.
	 *
	 * @param parent  the tree node to act as parent for the newly added goal
	 * @param child   the goal to be added as a child node to the tree
	 * @param recurse if true, will call this method for the child and each of its children, etc.
	 */
	protected void addChild(TreeItem parent, Goal child, boolean recurse, boolean secondLevel) {
		TreeItem node = new TreeItem();
		if(secondLevel) {
			node.setHTML("<p><font size = 3><i>" + child.getName() + "</i></font></p>");
		} else {
			node.setHTML("<font size = 2>" + child.getName() + "</font>");
		}
		secondLevel = false;
		node.setUserObject(child);
		parent.addItem(node);

		if (recurse) {
			for (int i = 0; i < child.getNumChildren(); i++)
				addChild(node, child.getChild(i), true, secondLevel);
		}

		/* If this node, as identified by its goal's id, was "expanded" in a
		 * previous incarnation of this tree, then expand it here again.
		 */
		if (expandedItems.contains(Integer.toString(child.getId())))
			node.setState(true, false);
	}


	public void onTreeItemSelected(TreeItem item) {

	}

	/**
	 * Remember the expansion state of this item, so that we can later
	 * return the tree to the same state when it is refreshed.
	 *
	 * @param item the item whose expanded/folded state changed
	 */
	public void onTreeItemStateChanged(TreeItem item) {
		/* Remember the expansion state for later tree refresh, based on goal ID. */
		Goal g;
		if (item.getUserObject() instanceof Goal)
			g = (Goal) item.getUserObject();
		else
			return;

		if (item.getState())
			expandedItems.add(Integer.toString(g.getId()));
		else
			expandedItems.remove(Integer.toString(g.getId()));
	}


	/**
	 * A command that refreshes the tree.
	 */
	protected Command updateTree = new Command() {
		public void execute() {
			tree.clear();
			populateTree(project);
		}
	};

	/**
	 * A command that first expands the selected tree item, and then
	 * refreshes the tree. This is typically used when a new child
	 * gets added to a node, so that the new child will be visible
	 * after the refresh.
	 */
	protected Command expandSelectedThenUpdateTree = new Command() {
		public void execute() {
			/* If the item being edited is not already expanded, "remember"
			 * it as expanded for after the refresh.
			 */
			TreeItem edited = tree.getSelectedItem();
			if (edited != null) {
				if (edited.getUserObject() instanceof Goal) {
					Goal g;
					g = (Goal) edited.getUserObject();
					String id;
					id = Integer.toString(g.getId());
					if (!expandedItems.contains(id))
						expandedItems.add(id);
				}
			}

			updateTree.execute();
		}
	};

	/**
	 * Collects functionality common to all editors related to Goal nodes.
	 */
	public abstract class GoalItemEditor extends DefaultItemEditor {
		protected ProjectInfo project;

		public GoalItemEditor(String iconPath, String title, Command onSuccess, ProjectInfo p) {
			super(iconPath, title, onSuccess);
			this.project = p;
		}

		protected Widget getInputWidget(Object userObject) {
			if (userObject == null || !(userObject instanceof Goal))
				throw new IllegalArgumentException("userObject must be a valid Goal");

			return configureWidget((Goal) userObject);
		}

		protected abstract Widget configureWidget(final Goal goal);
	}

	/**
	 * Lets you edit the Goal's name. Only matches to TreeItems
	 * whose userObject is a Goal.
	 */
	public class GoalEdit extends GoalItemEditor {
		public GoalEdit(Command onSuccess, ProjectInfo p) {
			super(DefaultItemEditor.iconPathEdit, "Edit", onSuccess, p);
		}

		public boolean matchesItem(TreeItem item) {
			/* Only display this for non-root items */
			return (item.getParentItem() != null);
		}

		protected Widget configureWidget(final Goal goal) {
			/* A panel for editing the name: text input, apply, cancel */
			VerticalPanel vpE = new VerticalPanel();
			final TextBox tbEdit = new TextBox();
			tbEdit.setVisibleLength(50);
			tbEdit.setText(goal.getName());
			vpE.add(tbEdit);

			HorizontalPanel hpE = new HorizontalPanel();

			Button btnEditDo = new Button("Apply");
			btnEditDo.addListener(new ButtonListenerAdapter() {
				public void onClick(Button button, EventObject e) {
					String newName = tbEdit.getText().trim();
					updateGoal(goal, newName);
				}
			});
			hpE.add(btnEditDo);

			Button btnEditCancel = new Button("Reset");
			btnEditCancel.addListener(new ButtonListenerAdapter() {
				public void onClick(Button button, EventObject e) {
					tbEdit.setText(goal.getName());
				}
			});
			hpE.add(btnEditCancel);
			vpE.add(hpE);

			return vpE;
		}

		protected void updateGoal(final Goal goal, String newName) {
			if (newName.equals(goal.getName()))
				return; /* No change was made */

			DataWriteService.App.getInstance().updateGoal(project.getId(), goal.getId(), newName, container.getCurrentAuthToken(), this);
		}
	}

	/**
	 * Lets you add a new goal as a child of the current goal,
	 * providing an input field for the name of the new goal.
	 * <p/>
	 * If added to a TreeItem that does not contain a goal as
	 * its userObject, then the new goal is added as a root-level
	 * goal, not as a sub-goal.
	 * <p/>
	 * This editor matches any TreeItem.
	 */
	public class GoalAdd extends GoalItemEditor {
		public GoalAdd(Command onSuccess, ProjectInfo p) {
			super(DefaultItemEditor.iconPathAdd, "Add a Sub-Goal", onSuccess, p);
		}

		public boolean matchesItem(TreeItem item) {
			return true;
		}

		protected Widget getInputWidget(Object userObject) {
			return configureWidget((Goal) userObject);
		}

		protected Widget configureWidget(final Goal goal) {
			/* A panel for editing the name: text input, apply, cancel */
			VerticalPanel vpA = new VerticalPanel();
			final TextBox tbAdd = new TextBox();
			tbAdd.setVisibleLength(50);
			vpA.add(tbAdd);
			HorizontalPanel hpA = new HorizontalPanel();
			Button btnAddDo = new Button("Create");
			btnAddDo.addListener(new ButtonListenerAdapter() {
				public void onClick(Button button, EventObject e) {
					addChildGoal(goal, tbAdd.getText().trim());
				}
			});
			hpA.add(btnAddDo);

			Button btnAddCancel = new Button("Reset");
			btnAddCancel.addListener(new ButtonListenerAdapter() {
				public void onClick(Button button, EventObject e) {
					tbAdd.setText("");
				}
			});
			hpA.add(btnAddCancel);
			vpA.add(hpA);

			return vpA;
		}

		protected void addChildGoal(final Goal goal, String newName) {
			if (newName.length() == 0) {
				displayError("You must enter a name for the new goal", null);
				return;
			}

			int parent = -1;
			if (goal != null)
				parent = goal.getId();

			DataWriteService.App.getInstance().createGoal(project.getId(), parent, newName, container.getCurrentAuthToken(), this);
		}
	}

	/**
	 * Displays a clickable link to remove the goal and all its sub-goals.
	 * <p/>
	 * This editor only matches TreeItem's whose userObject is a Goal.
	 */
	public class GoalRemove extends GoalItemEditor {
		public GoalRemove(Command onSuccess, ProjectInfo p) {
			super(DefaultItemEditor.iconPathRemove, "Remove", onSuccess, p);
		}

		protected Widget configureWidget(final Goal goal) {
			HTML rem = new HTML("<u style='cursor:hand'>Remove this goal (and all its sub goals)</u>");
			//Button rem = new Button("Remove this goal (and all its sub goals)");
			rem.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					MessageBox.confirm("Confirm", "Are you sure you want to do that? Your preferences may be affected.",  
							new MessageBox.ConfirmCallback() {  
						public void execute(String btnID) {  
							if(btnID.compareTo("yes") == 0) {
								removeGoalAndChildren(goal);
							}
						}  
					});
				}
			});

			return rem;
		}

		protected void removeGoalAndChildren(Goal goal) {
			DataWriteService.App.getInstance().removeGoal(project.getId(), goal.getId(), container.getCurrentAuthToken(), this);
		}
	}
}
